package com.bolt.support.spring.jpa.filter;


import com.bolt.common.collection.IteratorUtil;
import com.bolt.common.lang.Assert;
import com.bolt.common.ognl.OgnlCache;
import com.bolt.common.utils.ArrayUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.convention.exception.ServiceException;
import com.bolt.support.query.conditions.domain.Compare;
import com.bolt.support.query.conditions.domain.MatchType;
import com.bolt.support.query.conditions.domain.Related;
import com.bolt.support.query.conditions.domain.SqlClause;
import com.bolt.support.spring.jpa.jpql.SqlFunc;
import com.bolt.support.sqlconfig.base.ExpressionEvaluator;
import org.springframework.data.domain.Sort;

import java.util.Collection;
import java.util.List;

/**
 * Created by Administrator on 2019/5/22.
 */


public abstract class AbstractFilterWrapper<T, R, Children extends AbstractFilterWrapper<T, R, Children>>
        implements Compare<Children, R>, SqlFunc<Children, R>, Related<Children, R> {

    protected GroupPropertyFilter groupFilter = GroupPropertyFilter.buildDefaultAndGroupFilter();

    protected GroupPropertyFilter dynamicGroupFilter = GroupPropertyFilter.buildDefaultAndGroupFilter();

    protected final Children typedThis = (Children) this;

    protected abstract String getProperty(R property);


    /**
     * 等于 =
     *
     * @param property   字段
     * @param matchValue 值
     * @return
     */
    @Override
    public Children eq(R property, Object matchValue) {
        addCondition(property, MatchType.EQ, matchValue);
        return typedThis;
    }

    @Override
    public Children eq(SqlClause left, SqlClause right) {
        addCondition((String) left.render(false), MatchType.EQ, (String) left.render(false));
        return typedThis;
    }

    /**
     * 不等于 &lt;&gt;
     *
     * @param property   字段
     * @param matchValue 值
     * @return
     */
    @Override
    public Children ne(R property, Object matchValue) {
        addCondition(property, MatchType.NE, matchValue);
        return typedThis;
    }


    @Override
    public Children ne(SqlClause left, SqlClause right) {
        addCondition((String) left.render(false), MatchType.NE, (String) left.render(false));
        return typedThis;
    }

    /**
     * 大于 &gt;
     *
     * @param property   字段
     * @param matchValue 值
     * @return
     */
    @Override
    public Children gt(R property, Object matchValue) {
        addCondition(property, MatchType.GT, matchValue);
        return typedThis;
    }

    @Override
    public Children gt(SqlClause left, SqlClause right) {
        addCondition((String) left.render(false), MatchType.GT, (String) left.render(false));
        return typedThis;
    }

    /**
     * 大于等于 &gt;=
     *
     * @param property   字段
     * @param matchValue 值
     * @return
     */
    @Override
    public Children ge(R property, Object matchValue) {
        addCondition(property, MatchType.GE, matchValue);
        return typedThis;
    }

    @Override
    public Children ge(SqlClause left, SqlClause right) {
        addCondition((String) left.render(false), MatchType.GE, (String) left.render(false));
        return typedThis;
    }

    /**
     * 小于 &lt;
     *
     * @param property   字段
     * @param matchValue 值
     * @return
     */
    @Override
    public Children lt(R property, Object matchValue) {
        addCondition(property, MatchType.LT, matchValue);
        return typedThis;
    }

    @Override
    public Children lt(SqlClause left, SqlClause right) {
        addCondition((String) left.render(false), MatchType.LT, (String) left.render(false));
        return typedThis;
    }

    /**
     * 小于等于 &lt;=
     *
     * @param property
     * @param matchValue
     * @return
     */
    @Override
    public Children le(R property, Object matchValue) {
        addCondition(property, MatchType.LE, matchValue);
        return typedThis;
    }

    @Override
    public Children le(SqlClause left, SqlClause right) {
        addCondition((String) left.render(false), MatchType.LE, (String) left.render(false));
        return typedThis;
    }


    @Override
    public Children contains(R property, String matchValue) {
        addCondition(property, MatchType.CONTAINS, matchValue);
        return typedThis;
    }

    /**
     * BETWEEN 值1 AND 值2
     *
     * @param property 字段
     * @param val1     值1
     * @param val2     值2
     * @return children
     */
    @Override
    public Children between(R property, Object val1, Object val2) {
        addCondition(property, MatchType.BT, val1, val2);
        return typedThis;
    }

    /**
     * NOT BETWEEN 值1 AND 值2
     *
     * @param property 字段
     * @param val1     值1
     * @param val2     值2
     * @return children
     */
    @Override
    public Children notBetween(R property, Object val1, Object val2) {
        addCondition(property, MatchType.NBT, val1, val2);
        return typedThis;
    }

    /**
     * LIKE '%值%'
     *
     * @param property 字段
     * @param val      值
     * @return children
     */
    @Override
    public Children like(R property, String val) {
        addCondition(property, MatchType.CN, val);
        return typedThis;
    }

    /**
     * NOT LIKE '%值%'
     *
     * @param property 字段
     * @param val      值
     * @return children
     */
    @Override
    public Children notLike(R property, String val) {
        addCondition(property, MatchType.NC, val);
        return typedThis;
    }

    /**
     * LIKE '%值'
     *
     * @param property 字段
     * @param val      值
     * @return children
     */
    @Override
    public Children likeLeft(R property, String val) {
        addCondition(property, MatchType.EW, val);
        return typedThis;
    }

    /**
     * LIKE '值%'
     *
     * @param property 字段
     * @param val      值
     * @return children
     */
    @Override
    public Children likeRight(R property, String val) {
        addCondition(property, MatchType.BW, val);
        return typedThis;
    }

    /**
     * 字段 IS NULL
     * <p>例: isNull("name")</p>
     *
     * @param property 字段
     * @return children
     */
    @Override
    public Children isNull(R property) {
        addCondition(property, MatchType.NU, new Boolean(true));
        return typedThis;
    }

    /**
     * 字段 IS NOT NULL
     * <p>例: isNotNull("name")</p>
     *
     * @param property 字段
     * @return children
     */
    @Override
    public Children isNotNull(R property) {
        addCondition(property, MatchType.NN, new Boolean(true));
        return typedThis;
    }

    @Override
    public Children isBlank(R property) {
        addCondition(property, MatchType.BK, new Boolean(true));
        return typedThis;
    }

    @Override
    public Children isNotBlank(R property) {
        addCondition(property, MatchType.NB, new Boolean(true));
        return typedThis;
    }

    /**
     * 字段 IN (value.get(0), value.get(1), ...)
     * <p>例: in("id", Arrays.asList(1, 2, 3, 4, 5))</p>
     * <p>
     * <li> 如果集合为 empty 则不会进行 sql 拼接 </li>
     *
     * @param property 执行条件
     * @param coll     数据集合
     * @return children
     */
    @Override
    public Children in(R property, Collection<?> coll) {
        addCondition(property, MatchType.IN, coll);
        return typedThis;
    }

    @Override
    public Children notIn(R property, Collection<?> coll) {
        addCondition(property, MatchType.NI, coll);
        return typedThis;
    }

    @Override
    public Children and() {
        addRelatedCondition(this.groupFilter, GroupPropertyFilter.GROUP_OPERATION_AND);
        return typedThis;
    }

    @Override
    public Children or() {
        addRelatedCondition(this.groupFilter, GroupPropertyFilter.GROUP_OPERATION_OR);
        return typedThis;
    }

    @Override
    public Children and(R left, MatchType ops, Object... matchValue) {
        if (ArrayUtil.isNotEmpty(matchValue)) {
            addRelatedCondition(this.groupFilter, GroupPropertyFilter.GROUP_OPERATION_AND);
        } else {
            addRelatedCondition(this.dynamicGroupFilter, GroupPropertyFilter.GROUP_OPERATION_AND);
        }
        addCondition(left, ops, matchValue);
        return typedThis;
    }

    @Override
    public Children and(R left, MatchType ops, R right) {
        addRelatedCondition(this.groupFilter, GroupPropertyFilter.GROUP_OPERATION_AND);
        addCondition(left, ops, right);
        return typedThis;
    }


    @Override
    public Children or(R left, MatchType ops, Object... matchValue) {
        if (ArrayUtil.isNotEmpty(matchValue)) {
            addRelatedCondition(this.groupFilter, GroupPropertyFilter.GROUP_OPERATION_OR);
        } else {
            addRelatedCondition(this.dynamicGroupFilter, GroupPropertyFilter.GROUP_OPERATION_OR);
        }
        addCondition(left, ops, matchValue);
        return typedThis;
    }


    @Override
    public Children and(String test, R left, MatchType matchType) {
        addRelatedCondition(this.dynamicGroupFilter, GroupPropertyFilter.GROUP_OPERATION_AND);
        PropertyFilter filter = new PropertyFilter(matchType, test, left);
        this.dynamicGroupFilter.append(filter);
        return typedThis;
    }

    @Override
    public Children or(String test, R left, MatchType matchType) {
        addRelatedCondition(this.dynamicGroupFilter, GroupPropertyFilter.GROUP_OPERATION_OR);
        PropertyFilter filter = new PropertyFilter(matchType, test, left);
        this.dynamicGroupFilter.append(filter);
        return typedThis;
    }

    @Override
    public Children or(R left, MatchType ops, R right) {
        addRelatedCondition(this.groupFilter, GroupPropertyFilter.GROUP_OPERATION_OR);
        addCondition(left, ops, right);
        return typedThis;
    }

    private final static String BLANK_STR_EXPRS = "{} != null and {} != ''";

    protected Children addCondition(R property, MatchType matchType, Object... val) {
        Assert.notNull(val);
        PropertyFilter filter;
        if (val.length > 1) {
            filter = new PropertyFilter(matchType, getProperty(property), val);
            this.groupFilter.append(filter);
        } else if (val.length == 1) {
            filter = new PropertyFilter(matchType, getProperty(property), val[0]);
            this.groupFilter.append(filter);
        } else {
            String test = StrUtil.format(BLANK_STR_EXPRS, getProperty(property), getProperty(property));
            filter = new PropertyFilter(matchType, test, getProperty(property));
            this.dynamicGroupFilter.append(filter);
        }

        return typedThis;
    }

    protected Children addCondition(String left, MatchType matchType, String right) {
        PropertyFilter filter = new PropertyFilter(left, matchType, right);
        this.groupFilter.append(filter);
        return typedThis;
    }

    @Override
    public Children alias(String alias) {
        PropertyFilter filter = groupFilter.getLastFilter();
        filter.setAlias(alias);
        return typedThis;
    }


    /**
     * 排序：ORDER BY 字段, ...
     * <p>例: orderBy(true, "id", "name")</p>
     *
     * @param isAsc   是否是 ASC 排序
     * @param columns 字段数组
     * @return children
     */
    @Override
    public Children orderBy(boolean isAsc, R... columns) {
        Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
        Sort sort = Sort.unsorted();
        for (R r : columns) {
            sort = sort.and(Sort.by(direction, getProperty(r)));
        }
        this.groupFilter.setSort(sort);
        return typedThis;
    }

    protected Children addRelatedCondition(GroupPropertyFilter groupFilter, String related) {
        int groupSize = groupFilter.getGroups().size();
        int filterSize = groupFilter.getFilters().size();
        GroupPropertyFilter last;
        if (groupSize == 0 && filterSize <= 1) {
            last = groupFilter;
            last.setGroupType(related);
        } else {
            GroupPropertyFilter group = new GroupPropertyFilter(related);
            this.groupFilter.append(group);
        }

        return typedThis;
    }

    public GroupPropertyFilter build() {
        return this.groupFilter;
    }

    public GroupPropertyFilter build(Object param) {
        buildPropertyFilter(this.dynamicGroupFilter, param);
        return groupFilter;
    }

    private void buildPropertyFilter(GroupPropertyFilter groupFilter, Object param) {
        ExpressionEvaluator evaluator = new ExpressionEvaluator();

        List<PropertyFilter> filters = groupFilter.getFilters();
        if(IteratorUtil.isNotEmpty(filters)){
            handleValue(param, evaluator,
                    GroupPropertyFilter.buildDefaultAndGroupFilter(filters.toArray(new PropertyFilter[]{})));
        }

        List<GroupPropertyFilter> groupPropertyFilters = groupFilter.getGroups();
        for (GroupPropertyFilter groupPropertyFilter : groupPropertyFilters) {
            handleValue(param, evaluator, groupPropertyFilter);
        }
    }

    private void handleValue(Object param, ExpressionEvaluator evaluator, GroupPropertyFilter groupPropertyFilter) {
        List<PropertyFilter> filters = groupPropertyFilter.getFilters();
        for (PropertyFilter filter : filters) {
            if (StrUtil.isNotBlank(filter.getTest())
                    && evaluator.evaluateBoolean(filter.getTest(), param)) {
                String property = StrUtil.isBlank(filter.getAlias()) ? filter.getLeftProperty() : filter.getAlias();

                try {
                    Object value = OgnlCache.getValue(property, param);
                    filter.setMatchValue(value);
                    addRelatedCondition(this.groupFilter, groupPropertyFilter.getGroupType());
                    this.groupFilter.append(filter);
                } catch (Exception ex) {
                    throw new ServiceException(
                            "Error evaluating expression '" + property
                                    + "'. Cause: " + ex, ex);
                }
            }
        }
    }

}

 